/*
  zm.c - zmodem protocol handling lowlevelstuff
  Copyright (C) until 1998 Chuck Forsberg (OMEN Technology Inc)
  Copyright (C) 1996, 1997 Uwe Ohse

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.

  originally written by Chuck Forsberg
*/
/* historical comment: -- uwe
 *   Z M . C
 *    ZMODEM protocol primitives
 *    05-09-88  Chuck Forsberg Omen Technology Inc
 *
 * Entry point Functions:
 *	zsbhdr(type, hdr) send binary header
 *	zshhdr(type, hdr) send hex header
 *	zgethdr(hdr, eflag) receive header - binary or hex
 *	zsdata(buf, len, frameend) send data
 *	zrdata(buf, len, bytes_received) receive data
 *	stohdr(pos) store position data in Txhdr
 *	long rclhdr(hdr) recover position offset from header
 */


#include "zglobal.h"

#include <stdio.h>

unsigned int Rxtimeout = 100;   /* Tenths of seconds to wait for something */

/* Globals used by ZMODEM functions */
int Rxframeind;                 /* ZBIN ZBIN32, or ZHEX type of frame received */
int Rxtype;                     /* Type of header received */
char Rxhdr[4];                  /* Received header */
char Txhdr[4];                  /* Transmitted header */
long Txpos;                     /* Transmitted file position */
int Txfcs32;                    /* TRUE means send binary frames with 32 bit FCS */
int Crc32t;                     /* Display flag indicating 32 bit CRC being sent */
int Crc32;                      /* Display flag indicating 32 bit CRC being received */
int Znulls;                     /* Number of nulls to send at beginning of ZDATA hdr */
char Attn[ZATTNLEN + 1];        /* Attention string rx sends to tx on err */

static char lastsent;           /* Last char we sent */
int turbo_escape;
int bytes_per_error = 0;

#define badcrc _("Bad CRC")
/* static char *badcrc = "Bad CRC"; */
static inline int noxrd7(void);
static inline int zdlread(void);
static int zdlread2(int);
static inline int zgeth1(void);
static void zputhex(int c, char *pos);
static inline int zgethex(void);
static int zrbhdr(char *hdr);
static int zrbhdr32(char *hdr);
static int zrhhdr(char *hdr);
static char zsendline_tab[256];
static int zrdat32(char *buf, int length, size_t *);
static void zsbh32(char *hdr, int type);

extern int zmodem_requested;

#define sendline(c) raw_ochar((c) & 0377)
#define xsendline(c) raw_ochar(c)

void zmodem_error(int status, int errnum, const char *message, ...)
{
    if (status)
        exit(status);
}

/*
 * Read a character from the modem line with timeout.
 *  Eat parity, XON and XOFF characters.
 */
static inline int noxrd7(void)
{
    register int c;

    for (;;) {
        if ((c = READLINE_PF(Rxtimeout)) < 0)
            return c;
        switch (c &= 0177) {
        case XON:
        case XOFF:
            continue;
        default:
            if (Zctlesc && !(c & 0140))
                continue;
        case '\r':
        case '\n':
        case ZDLE:
            return c;
        }
    }
}

static inline int zgeth1(void)
{
    register int c, n;

    if ((c = noxrd7()) < 0)
        return c;
    n = c - '0';
    if (n > 9)
        n -= ('a' - ':');
    if (n & ~0xF)
        return ERROR;
    if ((c = noxrd7()) < 0)
        return c;
    c -= '0';
    if (c > 9)
        c -= ('a' - ':');
    if (c & ~0xF)
        return ERROR;
    c += (n << 4);
    return c;
}

/* Decode two lower case hex digits into an 8 bit byte value */
static inline int zgethex(void)
{
    register int c;

    c = zgeth1();
    return c;
}

/*
 * Read a byte, checking for ZMODEM escape encoding
 *  including CAN*5 which represents a quick abort
 */
static inline int zdlread(void)
{
    int c;

    /* Quick check for non control characters */
    if ((c = READLINE_PF(Rxtimeout)) & 0140)
        return c;
    return zdlread2(c);
}

/* no, i don't like gotos. -- uwe */
static int zdlread2(int c)
{
    goto jump_over;             /* bad style */

  again:
    /* Quick check for non control characters */
    if ((c = READLINE_PF(Rxtimeout)) & 0140)
        return c;
  jump_over:
    switch (c) {
    case ZDLE:
        break;
    case XON:
    case (XON | 0200):
    case XOFF:
    case (XOFF | 0200):
        goto again;
    default:
        if (Zctlesc && !(c & 0140)) {
            goto again;
        }
        return c;
    }
  again2:
    if ((c = READLINE_PF(Rxtimeout)) < 0)
        return c;
    if (c == CAN && (c = READLINE_PF(Rxtimeout)) < 0)
        return c;
    if (c == CAN && (c = READLINE_PF(Rxtimeout)) < 0)
        return c;
    if (c == CAN && (c = READLINE_PF(Rxtimeout)) < 0)
        return c;
    switch (c) {
    case CAN:
        return GOTCAN;
    case ZCRCE:
    case ZCRCG:
    case ZCRCQ:
    case ZCRCW:
        return (c | GOTOR);
    case ZRUB0:
        return 0177;
    case ZRUB1:
        return 0377;
    case XON:
    case (XON | 0200):
    case XOFF:
    case (XOFF | 0200):
        goto again2;
    default:
        if (Zctlesc && !(c & 0140)) {
            goto again2;
        }
        if ((c & 0140) == 0100)
            return (c ^ 0100);
        break;
    }
    return ERROR;
}



/*
 * Send character c with ZMODEM escape sequence encoding.
 *  Escape XON, XOFF. Escape CR following @ (Telenet net escape)
 */
inline void zsendline(int c)
{

    switch (zsendline_tab[(unsigned) (c &= 0377)]) {
    case 0:
        xsendline(lastsent = c);
        break;
    case 1:
        xsendline(ZDLE);
        c ^= 0100;
        xsendline(lastsent = c);
        break;
    case 2:
        if ((lastsent & 0177) != '@') {
            xsendline(lastsent = c);
        } else {
            xsendline(ZDLE);
            c ^= 0100;
            xsendline(lastsent = c);
        }
        break;
    }
}

static inline void zsendline_s(const char *s, size_t count)
{
    const char *end = s + count;

    while (s != end) {
        int last_esc = 0;
        const char *t = s;

        while (t != end) {
            last_esc = zsendline_tab[(unsigned) ((*t) & 0377)];
            if (last_esc)
                break;
            t++;
        }
        if (t != s) {
            raw_write(0, s, (t - s));
            lastsent = t[-1];
            s = t;
        }
        if (last_esc) {
            int c = *s;

            switch (last_esc) {
            case 0:
                xsendline(lastsent = c);
                break;
            case 1:
                xsendline(ZDLE);
                c ^= 0100;
                xsendline(lastsent = c);
                break;
            case 2:
                if ((lastsent & 0177) != '@') {
                    xsendline(lastsent = c);
                } else {
                    xsendline(ZDLE);
                    c ^= 0100;
                    xsendline(lastsent = c);
                }
                break;
            }
            s++;
        }
    }
}


/* Send ZMODEM binary header hdr of type type */
void zsbhdr(int type, char *hdr)
{
    register int n;
    register unsigned short crc;

    if (type == ZDATA)
        for (n = Znulls; --n >= 0;)
            xsendline(0);

    xsendline(ZPAD);
    xsendline(ZDLE);

    Crc32t = Txfcs32;
    if (Crc32t)
        zsbh32(hdr, type);
    else {
        xsendline(ZBIN);
        zsendline(type);
        crc = updcrc(type, 0);

        for (n = 4; --n >= 0; ++hdr) {
            zsendline(*hdr);
            crc = updcrc((0377 & *hdr), crc);
        }
        crc = updcrc(0, updcrc(0, crc));
        zsendline(crc >> 8);
        zsendline(crc);
    }
    //if (type != ZDATA)
        //flushmo();
}


/* Send ZMODEM binary header hdr of type type */
static void zsbh32(char *hdr, int type)
{
    register int n;
    register unsigned long crc;

    xsendline(ZBIN32);
    zsendline(type);
    crc = 0xFFFFFFFFL;
    crc = UPDC32(type, crc);

    for (n = 4; --n >= 0; ++hdr) {
        crc = UPDC32((0377 & *hdr), crc);
        zsendline(*hdr);
    }
    crc = ~crc;
    for (n = 4; --n >= 0;) {
        zsendline((int) crc);
        crc >>= 8;
    }
}

/* Send ZMODEM HEX header hdr of type type */
void zshhdr(int type, char *hdr)
{
    register int n;
    register unsigned short crc;
    char s[30];
    size_t len;

    s[0] = ZPAD;
    s[1] = ZPAD;
    s[2] = ZDLE;
    s[3] = ZHEX;
    zputhex(type & 0x7f, s + 4);
    len = 6;
    Crc32t = 0;

    crc = updcrc((type & 0x7f), 0);
    for (n = 4; --n >= 0; ++hdr) {
        zputhex(*hdr, s + len);
        len += 2;
        crc = updcrc((0377 & *hdr), crc);
    }
    crc = updcrc(0, updcrc(0, crc));
    zputhex(crc >> 8, s + len);
    zputhex(crc, s + len + 2);
    len += 4;

    /* Make it printable on remote machine */
    s[len++] = 015;
    s[len++] = 0212;
    /*
     * Uncork the remote in case a fake XOFF has stopped data flow
     */
    if (type != ZFIN && type != ZACK) {
        s[len++] = 021;
    }
    //flushmo();
    raw_write(0, s, len);
}

/*
 * Send binary array buf of length length, with ending ZDLE sequence frameend
 */
void zsdata(const char *buf, size_t length, int frameend)
{
    register unsigned short crc;

    crc = 0;
    do {
        zsendline(*buf);
        crc = updcrc((0377 & *buf), crc);
        buf++;
    } while (--length > 0);
    xsendline(ZDLE);
    xsendline(frameend);
    crc = updcrc(frameend, crc);

    crc = updcrc(0, updcrc(0, crc));
    zsendline(crc >> 8);
    zsendline(crc);
    if (frameend == ZCRCW) {
        xsendline(XON);
        //flushmo();
    }
}

void zsda32(const char *buf, size_t length, int frameend)
{
    int c;
    unsigned long crc;
    int i;

    crc = 0xFFFFFFFFL;
    zsendline_s(buf, length);
    for (; length; length--) {
        c = *buf & 0377;
        crc = UPDC32(c, crc);
        buf++;
    }
    xsendline(ZDLE);
    xsendline(frameend);
    crc = UPDC32(frameend, crc);

    crc = ~crc;
    for (i = 4; --i >= 0;) {
        c = (int) crc;
        if (c & 0140)
            xsendline(lastsent = c);
        else
            zsendline(c);
        crc >>= 8;
    }
    if (frameend == ZCRCW) {
        xsendline(XON);
        //flushmo();
    }
}

#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ <= 4)
#  undef DEBUG_BLOCKSIZE
#endif

#ifdef DEBUG_BLOCKSIZE
struct debug_blocksize {
    int size;
    long count;
};
struct debug_blocksize blocksizes[] = {
    {32, 0},
    {64, 0},
    {128, 0},
    {256, 0},
    {512, 0},
    {1024, 0},
    {2048, 0},
    {4096, 0},
    {8192, 0},
    {0, 0}
};
static inline void count_blk(int size)
{
    int i;

    for (i = 0; blocksizes[i].size; i++) {
        if (blocksizes[i].size == size) {
            blocksizes[i].count++;
            return;
        }
    }
    blocksizes[i].count++;
}

static void printout_blocksizes(void)
{
    int i;

    for (i = 0; blocksizes[i].size; i++) {
        if (blocksizes[i].count) {
        }
    }
    if (blocksizes[i].count) {
    }
}

#define COUNT_BLK(x) count_blk(x)
#else
#define COUNT_BLK(x)
#endif

/*
 * Receive array buf of max length with ending ZDLE sequence
 *  and CRC.  Returns the ending character or error code.
 *  NB: On errors may store length+1 bytes!
 */
int zrdata(char *buf, int length, size_t * bytes_received)
{
    register int c;
    register unsigned short crc;
    register char *end;
    register int d;

    *bytes_received = 0;
    if (Rxframeind == ZBIN32)
        return zrdat32(buf, length, bytes_received);

    crc = 0;
    end = buf + length;
    while (buf <= end) {
        if ((c = zdlread()) & ~0377) {
          crcfoo:
            switch (c) {
            case GOTCRCE:
            case GOTCRCG:
            case GOTCRCQ:
            case GOTCRCW:
                {
                    d = c;
                    c &= 0377;
                    crc = updcrc(c, crc);
                    if ((c = zdlread()) & ~0377)
                        goto crcfoo;
                    crc = updcrc(c, crc);
                    if ((c = zdlread()) & ~0377)
                        goto crcfoo;
                    crc = updcrc(c, crc);
                    if (crc & 0xFFFF) {
                        return ERROR;
                    }
                    *bytes_received = length - (end - buf);
                    COUNT_BLK(*bytes_received);
                    return d;
                }
            case GOTCAN:
                return ZCAN;
            case TIMEOUT:
                return c;
            default:
                return c;
            }
        }
        *buf++ = c;
        crc = updcrc(c, crc);
    }
    return ERROR;
}

static int zrdat32(char *buf, int length, size_t * bytes_received)
{
    register int c;
    register unsigned long crc;
    register char *end;
    register int d;

    crc = 0xFFFFFFFFL;
    end = buf + length;
    while (buf <= end) {
        if ((c = zdlread()) & ~0377) {
          crcfoo:
            switch (c) {
            case GOTCRCE:
            case GOTCRCG:
            case GOTCRCQ:
            case GOTCRCW:
                d = c;
                c &= 0377;
                crc = UPDC32(c, crc);
                if ((c = zdlread()) & ~0377)
                    goto crcfoo;
                crc = UPDC32(c, crc);
                if ((c = zdlread()) & ~0377)
                    goto crcfoo;
                crc = UPDC32(c, crc);
                if ((c = zdlread()) & ~0377)
                    goto crcfoo;
                crc = UPDC32(c, crc);
                if ((c = zdlread()) & ~0377)
                    goto crcfoo;
                crc = UPDC32(c, crc);
                if (crc != 0xDEBB20E3) {
                    return ERROR;
                }
                *bytes_received = length - (end - buf);
                COUNT_BLK(*bytes_received);
                return d;
            case GOTCAN:
                return ZCAN;
            case TIMEOUT:
                return c;
            default:
                return c;
            }
        }
        *buf++ = c;
        crc = UPDC32(c, crc);
    }
    return ERROR;
}

/*
 * Read a ZMODEM header to hdr, either binary or hex.
 *  eflag controls local display of non zmodem characters:
 *	0:  no display
 *	1:  display printing characters only
 *	2:  display all non ZMODEM characters
 *  On success, set Zmodem to 1, set Rxpos and return type of header.
 *   Otherwise return negative on error.
 *   Return ERROR instantly if ZCRCW sequence, for fast error recovery.
 */
int zgethdr(char *hdr, int eflag, size_t * Rxpos)
{
    register int c, cancount;
    unsigned int max_garbage;   /* Max bytes before start of frame */
    size_t rxpos = 0;           /* keep gcc happy */

    max_garbage = Zrwindow + Baudrate;
    Rxframeind = Rxtype = 0;

  startover:
    cancount = 5;
  again:
    /* Return immediate ERROR if ZCRCW sequence seen */
    switch (c = READLINE_PF(Rxtimeout)) {
    case RCDO:
    case TIMEOUT:
        goto fifi;
    case CAN:
      gotcan:
        if (--cancount <= 0) {
            c = ZCAN;
            goto fifi;
        }
        switch (c = READLINE_PF(1)) {
        case TIMEOUT:
            goto again;
        case ZCRCW:
            c = ERROR;
            /* **** FALL THRU TO **** */
        case RCDO:
            goto fifi;
        default:
            break;
        case CAN:
            if (--cancount <= 0) {
                c = ZCAN;
                goto fifi;
            }
            goto again;
        }
        /* **** FALL THRU TO **** */
    default:
      agn2:
        if (--max_garbage == 0) {
            return (ERROR);
        }
        goto startover;
    case ZPAD | 0200:          /* This is what we want. */
    case ZPAD:                 /* This is what we want. */
        break;
    }
    cancount = 5;
  splat:
    switch (c = noxrd7()) {
    case ZPAD:
        goto splat;
    case RCDO:
    case TIMEOUT:
        goto fifi;
    default:
        goto agn2;
    case ZDLE:                 /* This is what we want. */
        break;
    }

    switch (c = noxrd7()) {
    case RCDO:
    case TIMEOUT:
        goto fifi;
    case ZBIN:
        Rxframeind = ZBIN;
        Crc32 = FALSE;
        c = zrbhdr(hdr);
        break;
    case ZBIN32:
        Crc32 = Rxframeind = ZBIN32;
        c = zrbhdr32(hdr);
        break;
    case ZHEX:
        Rxframeind = ZHEX;
        Crc32 = FALSE;
        c = zrhhdr(hdr);
        break;
    case CAN:
        goto gotcan;
    default:
        goto agn2;
    }
    rxpos = hdr[ZP3] & 0377;
    rxpos = (rxpos << 8) + (hdr[ZP2] & 0377);
    rxpos = (rxpos << 8) + (hdr[ZP1] & 0377);
    rxpos = (rxpos << 8) + (hdr[ZP0] & 0377);
  fifi:
    switch (c) {
    case GOTCAN:
        c = ZCAN;
        /* **** FALL THRU TO **** */
    case ZNAK:
    case ZCAN:
    case ERROR:
    case TIMEOUT:
    case RCDO:
        /* **** FALL THRU TO **** */
    default:
	;
    }
    if (Rxpos)
        *Rxpos = rxpos;
    return c;
}

/* Receive a binary style header (type and position) */
static int zrbhdr(char *hdr)
{
    register int c, n;
    register unsigned short crc;

    if ((c = zdlread()) & ~0377)
        return c;
    Rxtype = c;
    crc = updcrc(c, 0);

    for (n = 4; --n >= 0; ++hdr) {
        if ((c = zdlread()) & ~0377)
            return c;
        crc = updcrc(c, crc);
        *hdr = c;
    }
    if ((c = zdlread()) & ~0377)
        return c;
    crc = updcrc(c, crc);
    if ((c = zdlread()) & ~0377)
        return c;
    crc = updcrc(c, crc);
    if (crc & 0xFFFF) {
        return ERROR;
    }
    protocol = ZM_ZMODEM;
    zmodem_requested = TRUE;
    return Rxtype;
}

/* Receive a binary style header (type and position) with 32 bit FCS */
static int zrbhdr32(char *hdr)
{
    register int c, n;
    register unsigned long crc;

    if ((c = zdlread()) & ~0377)
        return c;
    Rxtype = c;
    crc = 0xFFFFFFFFL;
    crc = UPDC32(c, crc);

    for (n = 4; --n >= 0; ++hdr) {
        if ((c = zdlread()) & ~0377)
            return c;
        crc = UPDC32(c, crc);
        *hdr = c;
    }
    for (n = 4; --n >= 0;) {
        if ((c = zdlread()) & ~0377)
            return c;
        crc = UPDC32(c, crc);
    }
    if (crc != 0xDEBB20E3) {
        return ERROR;
    }
    protocol = ZM_ZMODEM;
    zmodem_requested = TRUE;
    return Rxtype;
}


/* Receive a hex style header (type and position) */
static int zrhhdr(char *hdr)
{
    register int c;
    register unsigned short crc;
    register int n;

    if ((c = zgethex()) < 0)
        return c;
    Rxtype = c;
    crc = updcrc(c, 0);

    for (n = 4; --n >= 0; ++hdr) {
        if ((c = zgethex()) < 0)
            return c;
        crc = updcrc(c, crc);
        *hdr = c;
    }
    if ((c = zgethex()) < 0)
        return c;
    crc = updcrc(c, crc);
    if ((c = zgethex()) < 0)
        return c;
    crc = updcrc(c, crc);
    if (crc & 0xFFFF) {
        return ERROR;
    }
    switch (c = READLINE_PF(1)) {
    case 0215:
        /* **** FALL THRU TO **** */
    case 015:
        /* Throw away possible cr/lf */
        READLINE_PF(1);
        break;
    }
    protocol = ZM_ZMODEM;
    zmodem_requested = TRUE;
    return Rxtype;
}

/* Write a byte as two hex digits */
static void zputhex(int c, char *pos)
{
    static char digits[] = "0123456789abcdef";

    pos[0] = digits[(c & 0xF0) >> 4];
    pos[1] = digits[c & 0x0F];
}

void zsendline_init(void)
{
    int i;

    for (i = 0; i < 256; i++) {
        if (i & 0140)
            zsendline_tab[i] = 0;
        else {
            switch (i) {
            case ZDLE:
            case XOFF:         /* ^Q */
            case XON:          /* ^S */
            case (XOFF | 0200):
            case (XON | 0200):
                zsendline_tab[i] = 1;
                break;
            case 020:          /* ^P */
            case 0220:
                if (turbo_escape)
                    zsendline_tab[i] = 0;
                else
                    zsendline_tab[i] = 1;
                break;
            case 015:
            case 0215:
                if (Zctlesc)
                    zsendline_tab[i] = 1;
                else if (!turbo_escape)
                    zsendline_tab[i] = 2;
                else
                    zsendline_tab[i] = 0;
                break;
            default:
                if (Zctlesc)
                    zsendline_tab[i] = 1;
                else
                    zsendline_tab[i] = 0;
            }
        }
    }
}



/* Store pos in Txhdr */
void stohdr(size_t pos)
{
    long lpos = (long) pos;

    Txhdr[ZP0] = lpos;
    Txhdr[ZP1] = lpos >> 8;
    Txhdr[ZP2] = lpos >> 16;
    Txhdr[ZP3] = lpos >> 24;
}

/* Recover a long integer from a header */
long rclhdr(char *hdr)
{
    long l;

    l = (hdr[ZP3] & 0377);
    l = (l << 8) | (hdr[ZP2] & 0377);
    l = (l << 8) | (hdr[ZP1] & 0377);
    l = (l << 8) | (hdr[ZP0] & 0377);
    return l;
}

/* End of zm.c */
